(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.nipplejs = f()}})(function(){var define,module,exports;
    'use strict';
    
    // Constants
    var isTouch = !!('ontouchstart' in window);
    var isPointer = window.PointerEvent ? true : false;
    var isMSPointer = window.MSPointerEvent ? true : false;
    var events = {
        touch: {
            start: 'touchstart',
            move: 'touchmove',
            end: 'touchend, touchcancel'
        },
        mouse: {
            start: 'mousedown',
            move: 'mousemove',
            end: 'mouseup'
        },
        pointer: {
            start: 'pointerdown',
            move: 'pointermove',
            end: 'pointerup, pointercancel'
        },
        MSPointer: {
            start: 'MSPointerDown',
            move: 'MSPointerMove',
            end: 'MSPointerUp'
        }
    };
    var toBind;
    var secondBind = {};
    if (isPointer) {
        toBind = events.pointer;
    } else if (isMSPointer) {
        toBind = events.MSPointer;
    } else if (isTouch) {
        toBind = events.touch;
        secondBind = events.mouse;
    } else {
        toBind = events.mouse;
    }
    
    ///////////////////////
    ///      UTILS      ///
    ///////////////////////
    
    var u = {};
    u.distance = function (p1, p2) {
        var dx = p2.x - p1.x;
        var dy = p2.y - p1.y;
    
        return Math.sqrt((dx * dx) + (dy * dy));
    };
    
    u.angle = function(p1, p2) {
        var dx = p2.x - p1.x;
        var dy = p2.y - p1.y;
    
        return u.degrees(Math.atan2(dy, dx));
    };
    
    u.findCoord = function(p, d, a) {
        var b = {x: 0, y: 0};
        a = u.radians(a);
        b.x = p.x - d * Math.cos(a);
        b.y = p.y - d * Math.sin(a);
        return b;
    };
    
    u.radians = function(a) {
        return a * (Math.PI / 180);
    };
    
    u.degrees = function(a) {
        return a * (180 / Math.PI);
    };
    
    u.bindEvt = function (el, arg, handler) {
        var types = arg.split(/[ ,]+/g);
        var type;
        for (var i = 0; i < types.length; i += 1) {
            type = types[i];
            if (el.addEventListener) {
                el.addEventListener(type, handler, false);
            } else if (el.attachEvent) {
                el.attachEvent(type, handler);
            }
        }
    };
    
    u.unbindEvt = function (el, arg, handler) {
        var types = arg.split(/[ ,]+/g);
        var type;
        for (var i = 0; i < types.length; i += 1) {
            type = types[i];
            if (el.removeEventListener) {
                el.removeEventListener(type, handler);
            } else if (el.detachEvent) {
                el.detachEvent(type, handler);
            }
        }
    };
    
    u.trigger = function (el, type, data) {
        var evt = new CustomEvent(type, data);
        el.dispatchEvent(evt);
    };
    
    u.prepareEvent = function (evt) {
        evt.preventDefault();
        return evt.type.match(/^touch/) ? evt.changedTouches : evt;
    };
    
    u.getScroll = function () {
        var x = (window.pageXOffset !== undefined) ?
            window.pageXOffset :
            (document.documentElement || document.body.parentNode || document.body)
                .scrollLeft;
    
        var y = (window.pageYOffset !== undefined) ?
            window.pageYOffset :
            (document.documentElement || document.body.parentNode || document.body)
                .scrollTop;
        return {
            x: x,
            y: y
        };
    };
    
    u.applyPosition = function (el, pos) {
        if (pos.top || pos.right || pos.bottom || pos.left) {
            el.style.top = pos.top;
            el.style.right = pos.right;
            el.style.bottom = pos.bottom;
            el.style.left = pos.left;
        } else {
            el.style.left = pos.x + 'px';
            el.style.top = pos.y + 'px';
        }
    };
    
    u.getTransitionStyle = function (property, values, time) {
        var obj = u.configStylePropertyObject(property);
        for (var i in obj) {
            if (obj.hasOwnProperty(i)) {
                if (typeof values === 'string') {
                    obj[i] = values + ' ' + time;
                } else {
                    var st = '';
                    for (var j = 0, max = values.length; j < max; j += 1) {
                        st += values[j] + ' ' + time + ', ';
                    }
                    obj[i] = st.slice(0, -2);
                }
            }
        }
        return obj;
    };
    
    u.getVendorStyle = function (property, value) {
        var obj = u.configStylePropertyObject(property);
        for (var i in obj) {
            if (obj.hasOwnProperty(i)) {
                obj[i] = value;
            }
        }
        return obj;
    };
    
    u.configStylePropertyObject = function (prop) {
        var obj = {};
        obj[prop] = '';
        var vendors = ['webkit', 'Moz', 'o'];
        vendors.forEach(function (vendor) {
            obj[vendor + prop.charAt(0).toUpperCase() + prop.slice(1)] = '';
        });
        return obj;
    };
    
    u.extend = function (objA, objB) {
        for (var i in objB) {
            if (objB.hasOwnProperty(i)) {
                objA[i] = objB[i];
            }
        }
        return objA;
    };
    
    // Overwrite only what's already present
    u.safeExtend = function (objA, objB) {
        var obj = {};
        for (var i in objA) {
            if (objA.hasOwnProperty(i) && objB.hasOwnProperty(i)) {
                obj[i] = objB[i];
            } else if (objA.hasOwnProperty(i)) {
                obj[i] = objA[i];
            }
        }
        return obj;
    };
    
    // Map for array or unique item.
    u.map = function (ar, fn) {
        if (ar.length) {
            for (var i = 0, max = ar.length; i < max; i += 1) {
                fn(ar[i]);
            }
        } else {
            fn(ar);
        }
    };
    
    ///////////////////////
    ///   SUPER CLASS   ///
    ///////////////////////
    
    function Super () {};
    
    // Basic event system.
    Super.prototype.on = function (arg, cb) {
        var self = this;
        var types = arg.split(/[ ,]+/g);
        var type;
        self._handlers_ = self._handlers_ || {};
    
        for (var i = 0; i < types.length; i += 1) {
            type = types[i];
            self._handlers_[type] = self._handlers_[type] || [];
            self._handlers_[type].push(cb);
        }
        return self;
    };
    
    Super.prototype.off = function (type, cb) {
        var self = this;
        self._handlers_ = self._handlers_ || {};
    
        if (type === undefined) {
            self._handlers_ = {};
        } else if (cb === undefined) {
            self._handlers_[type] = null;
        } else if (self._handlers_[type] &&
                self._handlers_[type].indexOf(cb) >= 0) {
            self._handlers_[type].splice(self._handlers_[type].indexOf(cb), 1);
        }
    
        return self;
    };
    
    Super.prototype.trigger = function (arg, data) {
        var self = this;
        var types = arg.split(/[ ,]+/g);
        var type;
        self._handlers_ = self._handlers_ || {};
    
        for (var i = 0; i < types.length; i += 1) {
            type = types[i];
            if (self._handlers_[type] && self._handlers_[type].length) {
                self._handlers_[type].forEach(function (handler) {
                    handler.call(self, {
                        type: type,
                        target: self
                    }, data);
                });
            }
        }
    };
    
    // Configuration
    Super.prototype.config = function (options) {
        var self = this;
        self.options = self.defaults || {};
        if (options) {
            self.options = u.safeExtend(self.options, options);
        }
    };
    
    // Bind internal events.
    Super.prototype.bindEvt = function (el, type) {
        var self = this;
        self._domHandlers_ = self._domHandlers_ || {};
    
        self._domHandlers_[type] = function () {
            if (typeof self['on' + type] === 'function') {
                self['on' + type].apply(self, arguments);
            } else {
                console.warn('[WARNING] : Missing "on' + type + '" handler.');
            }
        };
    
        u.bindEvt(el, toBind[type], self._domHandlers_[type]);
    
        if (secondBind[type]) {
            // Support for both touch and mouse at the same time.
            u.bindEvt(el, secondBind[type], self._domHandlers_[type]);
        }
    
        return self;
    };
    
    // Unbind dom events.
    Super.prototype.unbindEvt = function (el, type) {
        var self = this;
        self._domHandlers_ = self._domHandlers_ || {};
    
        u.unbindEvt(el, toBind[type], self._domHandlers_[type]);
    
        if (secondBind[type]) {
            // Support for both touch and mouse at the same time.
            u.unbindEvt(el, secondBind[type], self._domHandlers_[type]);
        }
    
        delete self._domHandlers_[type];
    
        return this;
    };
    
    ///////////////////////
    ///   THE NIPPLE    ///
    ///////////////////////
    
    function Nipple (collection, options) {
        this.identifier = options.identifier;
        this.position = options.position;
        this.frontPosition = options.frontPosition;
        this.collection = collection;
    
        // Defaults
        this.defaults = {
            size: 100,
            threshold: 0.1,
            color: 'white',
            fadeTime: 250,
            dataOnly: false,
            restJoystick: true,
            restOpacity: 0.5,
            mode: 'dynamic',
            zone: document.body,
            lockX: false,
            lockY: false
        };
    
        this.config(options);
    
        // Overwrites
        if (this.options.mode === 'dynamic') {
            this.options.restOpacity = 0;
        }
    
        this.id = Nipple.id;
        Nipple.id += 1;
        this.buildEl()
            .stylize();
    
        // Nipple's API.
        this.instance = {
            el: this.ui.el,
            on: this.on.bind(this),
            off: this.off.bind(this),
            show: this.show.bind(this),
            hide: this.hide.bind(this),
            add: this.addToDom.bind(this),
            remove: this.removeFromDom.bind(this),
            destroy: this.destroy.bind(this),
            resetDirection: this.resetDirection.bind(this),
            computeDirection: this.computeDirection.bind(this),
            trigger: this.trigger.bind(this),
            position: this.position,
            frontPosition: this.frontPosition,
            ui: this.ui,
            identifier: this.identifier,
            id: this.id,
            options: this.options
        };
    
        return this.instance;
    };
    
    Nipple.prototype = new Super();
    Nipple.constructor = Nipple;
    Nipple.id = 0;
    
    // Build the dom element of the Nipple instance.
    Nipple.prototype.buildEl = function (options) {
        this.ui = {};
    
        if (this.options.dataOnly) {
            return this;
        }
    
        this.ui.el = document.createElement('div');
        this.ui.back = document.createElement('div');
        this.ui.front = document.createElement('div');
    
        this.ui.el.className = 'nipple collection_' + this.collection.id;
        this.ui.back.className = 'back';
        this.ui.front.className = 'front';
    
        this.ui.el.setAttribute('id', 'nipple_' + this.collection.id +
            '_' + this.id);
    
        this.ui.el.appendChild(this.ui.back);
        this.ui.el.appendChild(this.ui.front);
    
        return this;
    };
    
    // Apply CSS to the Nipple instance.
    Nipple.prototype.stylize = function () {
        if (this.options.dataOnly) {
            return this;
        }
        var animTime = this.options.fadeTime + 'ms';
        var borderStyle = u.getVendorStyle('borderRadius', '50%');
        var transitStyle = u.getTransitionStyle('transition', 'opacity', animTime);
        var styles = {};
        styles.el = {
            position: 'absolute',
            opacity: this.options.restOpacity,
            display: 'block',
            'zIndex': 999
        };
    
        styles.back = {
            position: 'absolute',
            display: 'block',
            width: this.options.size + 'px',
            height: this.options.size + 'px',
            marginLeft: -this.options.size / 2 + 'px',
            marginTop: -this.options.size / 2 + 'px',
            background: this.options.color,
            'opacity': '.5'
        };
    
        styles.front = {
            width: this.options.size / 2 + 'px',
            height: this.options.size / 2 + 'px',
            position: 'absolute',
            display: 'block',
            marginLeft: -this.options.size / 4 + 'px',
            marginTop: -this.options.size / 4 + 'px',
            background: this.options.color,
            'opacity': '.5'
        };
    
        u.extend(styles.el, transitStyle);
        u.extend(styles.back, borderStyle);
        u.extend(styles.front, borderStyle);
    
        this.applyStyles(styles);
    
        return this;
    };
    
    Nipple.prototype.applyStyles = function (styles) {
        // Apply styles
        for (var i in this.ui) {
            if (this.ui.hasOwnProperty(i)) {
                for (var j in styles[i]) {
                    this.ui[i].style[j] = styles[i][j];
                }
            }
        }
    
        return this;
    };
    
    // Inject the Nipple instance into DOM.
    Nipple.prototype.addToDom = function () {
        // We're not adding it if we're dataOnly or already in dom.
        if (this.options.dataOnly || document.body.contains(this.ui.el)) {
            return this;
        }
        this.options.zone.appendChild(this.ui.el);
        return this;
    };
    
    // Remove the Nipple instance from DOM.
    Nipple.prototype.removeFromDom = function () {
        if (this.options.dataOnly || !document.body.contains(this.ui.el)) {
            return this;
        }
        this.options.zone.removeChild(this.ui.el);
        return this;
    };
    
    // Entirely destroy this nipple
    Nipple.prototype.destroy = function () {
        clearTimeout(this.removeTimeout);
        clearTimeout(this.showTimeout);
        clearTimeout(this.restTimeout);
        this.trigger('destroyed', this.instance);
        this.removeFromDom();
        this.off();
    };
    
    // Fade in the Nipple instance.
    Nipple.prototype.show = function (cb) {
        var self = this;
    
        if (self.options.dataOnly) {
            return self;
        }
    
        clearTimeout(self.removeTimeout);
        clearTimeout(self.showTimeout);
        clearTimeout(self.restTimeout);
    
        self.addToDom();
    
        self.restCallback();
    
        setTimeout(function () {
            self.ui.el.style.opacity = 1;
        }, 0);
    
        self.showTimeout = setTimeout(function () {
            self.trigger('shown', self.instance);
            if (typeof cb === 'function') {
                cb.call(this);
            }
        }, self.options.fadeTime);
    
        return self;
    };
    
    // Fade out the Nipple instance.
    Nipple.prototype.hide = function (cb) {
        var self = this;
    
        if (self.options.dataOnly) {
            return self;
        }
    
        self.ui.el.style.opacity = self.options.restOpacity;
    
        clearTimeout(self.removeTimeout);
        clearTimeout(self.showTimeout);
        clearTimeout(self.restTimeout);
    
        self.removeTimeout = setTimeout(
            function () {
                var display = self.options.mode === 'dynamic' ? 'none' : 'block';
                self.ui.el.style.display = display;
                if (typeof cb === 'function') {
                    cb.call(self);
                }
    
                self.trigger('hidden', self.instance);
            },
            self.options.fadeTime
        );
        if (self.options.restJoystick) {
            self.restPosition();
        }
    
        return self;
    };
    
    Nipple.prototype.restPosition = function (cb) {
        var self = this;
        self.frontPosition = {
            x: 0,
            y: 0
        };
        var animTime = self.options.fadeTime + 'ms';
    
        var transitStyle = {};
        transitStyle.front = u.getTransitionStyle('transition',
            ['top', 'left'], animTime);
    
        var styles = {front: {}};
        styles.front = {
            left: self.frontPosition.x + 'px',
            top: self.frontPosition.y + 'px'
        };
    
        self.applyStyles(transitStyle);
        self.applyStyles(styles);
    
        self.restTimeout = setTimeout(
            function () {
                if (typeof cb === 'function') {
                    cb.call(self);
                }
                self.restCallback();
            },
            self.options.fadeTime
        );
    };
    
    Nipple.prototype.restCallback = function () {
        var self = this;
        var transitStyle = {};
        transitStyle.front = u.getTransitionStyle('transition', 'none', '');
        self.applyStyles(transitStyle);
        self.trigger('rested', self.instance);
    };
    
    Nipple.prototype.resetDirection = function () {
        // Fully rebuild the object to let the iteration possible.
        this.direction = {
            x: false,
            y: false,
            angle: false
        };
    };
    
    Nipple.prototype.computeDirection = function (obj) {
        var rAngle = obj.angle.radian;
        var angle45 = Math.PI / 4;
        var angle90 = Math.PI / 2;
        var direction, directionX, directionY;
    
        // Angular direction
        //     \  UP /
        //      \   /
        // LEFT       RIGHT
        //      /   \
        //     /DOWN \
        //
        if (
            rAngle > angle45 &&
            rAngle < (angle45 * 3) &&
            !obj.lockX
        ) {
            direction = 'up';
        } else if (
            rAngle > -angle45 &&
            rAngle <= angle45 &&
            !obj.lockY
        ) {
            direction = 'left';
        } else if (
            rAngle > (-angle45 * 3) &&
            rAngle <= -angle45 &&
            !obj.lockX
        ) {
            direction = 'down';
        } else if (!obj.lockY) {
            direction = 'right';
        }
    
        // Plain direction
        //    UP                 |
        // _______               | RIGHT
        //                  LEFT |
        //   DOWN                |
        if (!obj.lockY) {
            if (rAngle > -angle90 && rAngle < angle90) {
                directionX = 'left';
            } else {
                directionX = 'right';
            }
        }
    
        if (!obj.lockX) {
            if (rAngle > 0) {
                directionY = 'up';
            } else {
                directionY = 'down';
            }
        }
    
        if (obj.force > this.options.threshold) {
            var oldDirection = {};
            for (var i in this.direction) {
                if (this.direction.hasOwnProperty(i)) {
                    oldDirection[i] = this.direction[i];
                }
            }
    
            var same = {};
    
            this.direction = {
                x: directionX,
                y: directionY,
                angle: direction
            };
    
            obj.direction = this.direction;
    
            for (var i in oldDirection) {
                if (oldDirection[i] === this.direction[i]) {
                    same[i] = true;
                }
            }
    
            // If all 3 directions are the same, we don't trigger anything.
            if (same.x && same.y && same.angle) {
                return obj;
            }
    
            if (!same.x || !same.y) {
                this.trigger('plain', obj);
            }
    
            if (!same.x) {
                this.trigger('plain:' + directionX, obj);
            }
    
            if (!same.y) {
                this.trigger('plain:' + directionY, obj);
            }
    
            if (!same.angle) {
                this.trigger('dir dir:' + direction, obj);
            }
        }
        return obj;
    };
    
    /* global Nipple, Super */
    
    ///////////////////////////
    ///   THE COLLECTION    ///
    ///////////////////////////
    
    function Collection (manager, options) {
        var self = this;
        self.nipples = [];
        self.idles = [];
        self.actives = [];
        self.ids = [];
        self.pressureIntervals = {};
        self.manager = manager;
        self.id = Collection.id;
        Collection.id += 1;
    
        // Defaults
        self.defaults = {
            zone: document.body,
            multitouch: false,
            maxNumberOfNipples: 10,
            mode: 'dynamic',
            position: {top: 0, left: 0},
            catchDistance: 200,
            size: 100,
            threshold: 0.1,
            color: 'white',
            fadeTime: 250,
            dataOnly: false,
            restJoystick: true,
            restOpacity: 0.5,
            lockX: false,
            lockY: false
        };
    
        self.config(options);
    
        // Overwrites
        if (self.options.mode === 'static' || self.options.mode === 'semi') {
            self.options.multitouch = false;
        }
    
        if (!self.options.multitouch) {
            self.options.maxNumberOfNipples = 1;
        }
    
        self.updateBox();
        self.prepareNipples();
        self.bindings();
        self.begin();
    
        return self.nipples;
    }
    
    Collection.prototype = new Super();
    Collection.constructor = Collection;
    Collection.id = 0;
    
    Collection.prototype.prepareNipples = function () {
        var self = this;
        var nips = self.nipples;
    
        // Public API Preparation.
        nips.on = self.on.bind(self);
        nips.off = self.off.bind(self);
        nips.options = self.options;
        nips.destroy = self.destroy.bind(self);
        nips.ids = self.ids;
        nips.id = self.id;
        nips.processOnMove = self.processOnMove.bind(self);
        nips.processOnEnd = self.processOnEnd.bind(self);
        nips.get = function (id) {
            if (id === undefined) {
                return nips[0];
            }
            for (var i = 0, max = nips.length; i < max; i += 1) {
                if (nips[i].identifier === id) {
                    return nips[i];
                }
            }
            return false;
        };
    };
    
    Collection.prototype.bindings = function () {
        var self = this;
        // Touch start event.
        self.bindEvt(self.options.zone, 'start');
        // Avoid native touch actions (scroll, zoom etc...) on the zone.
        self.options.zone.style.touchAction = 'none';
        self.options.zone.style.msTouchAction = 'none';
    };
    
    Collection.prototype.begin = function () {
        var self = this;
        var opts = self.options;
    
        // We place our static nipple
        // if needed.
        if (opts.mode === 'static') {
            var nipple = self.createNipple(
                opts.position,
                self.manager.getIdentifier()
            );
            // Add it to the dom.
            nipple.add();
            // Store it in idles.
            self.idles.push(nipple);
        }
    };
    
    // Nipple Factory
    Collection.prototype.createNipple = function (position, identifier) {
        var self = this;
        var scroll = u.getScroll();
        var toPutOn = {};
        var opts = self.options;
    
        if (position.x && position.y) {
            toPutOn = {
                x: position.x -
                    (scroll.x + self.box.left),
                y: position.y -
                    (scroll.y + self.box.top)
            };
        } else if (
                position.top ||
                position.right ||
                position.bottom ||
                position.left
            ) {
    
            // We need to compute the position X / Y of the joystick.
            var dumb = document.createElement('DIV');
            dumb.style.display = 'hidden';
            dumb.style.top = position.top;
            dumb.style.right = position.right;
            dumb.style.bottom = position.bottom;
            dumb.style.left = position.left;
            dumb.style.position = 'absolute';
    
            opts.zone.appendChild(dumb);
            var dumbBox = dumb.getBoundingClientRect();
            opts.zone.removeChild(dumb);
    
            toPutOn = position;
            position = {
                x: dumbBox.left + scroll.x,
                y: dumbBox.top + scroll.y
            };
        }
    
        var nipple = new Nipple(self, {
            color: opts.color,
            size: opts.size,
            threshold: opts.threshold,
            fadeTime: opts.fadeTime,
            dataOnly: opts.dataOnly,
            restJoystick: opts.restJoystick,
            restOpacity: opts.restOpacity,
            mode: opts.mode,
            identifier: identifier,
            position: position,
            zone: opts.zone,
            frontPosition: {
                x: 0,
                y: 0
            }
        });
    
        if (!opts.dataOnly) {
            u.applyPosition(nipple.ui.el, toPutOn);
            u.applyPosition(nipple.ui.front, nipple.frontPosition);
        }
        self.nipples.push(nipple);
        self.trigger('added ' + nipple.identifier + ':added', nipple);
        self.manager.trigger('added ' + nipple.identifier + ':added', nipple);
    
        self.bindNipple(nipple);
    
        return nipple;
    };
    
    Collection.prototype.updateBox = function () {
        var self = this;
        self.box = self.options.zone.getBoundingClientRect();
    };
    
    Collection.prototype.bindNipple = function (nipple) {
        var self = this;
        var type;
        // Bubble up identified events.
        var handler = function (evt, data) {
            // Identify the event type with the nipple's id.
            type = evt.type + ' ' + data.id + ':' + evt.type;
            self.trigger(type, data);
        };
    
        // When it gets destroyed.
        nipple.on('destroyed', self.onDestroyed.bind(self));
    
        // Other events that will get bubbled up.
        nipple.on('shown hidden rested dir plain', handler);
        nipple.on('dir:up dir:right dir:down dir:left', handler);
        nipple.on('plain:up plain:right plain:down plain:left', handler);
    };
    
    Collection.prototype.pressureFn = function (touch, nipple, identifier) {
        var self = this;
        var previousPressure = 0;
        clearInterval(self.pressureIntervals[identifier]);
        // Create an interval that will read the pressure every 100ms
        self.pressureIntervals[identifier] = setInterval(function () {
            var pressure = touch.force || touch.pressure ||
                touch.webkitForce || 0;
            if (pressure !== previousPressure) {
                nipple.trigger('pressure', pressure);
                self.trigger('pressure ' +
                    nipple.identifier + ':pressure', pressure);
                previousPressure = pressure;
            }
        }.bind(self), 100);
    };
    
    Collection.prototype.onstart = function (evt) {
        var self = this;
        var opts = self.options;
        evt = u.prepareEvent(evt);
    
        // Update the box position
        self.updateBox();
    
        var process = function (touch) {
            // If we can create new nipples
            // meaning we don't have more active nipples than we should.
            if (self.actives.length < opts.maxNumberOfNipples) {
                self.processOnStart(touch);
            }
        };
    
        u.map(evt, process);
    
        // We ask upstream to bind the document
        // on 'move' and 'end'
        self.manager.bindDocument();
        return false;
    };
    
    Collection.prototype.processOnStart = function (evt) {
        var self = this;
        var opts = self.options;
        var indexInIdles;
        var identifier = self.manager.getIdentifier(evt);
        var pressure = evt.force || evt.pressure || evt.webkitForce || 0;
        var position = {
            x: evt.pageX,
            y: evt.pageY
        };
    
        var nipple = self.getOrCreate(identifier, position);
    
        // Update its touch identifier
        if (nipple.identifier !== identifier) {
            self.manager.removeIdentifier(nipple.identifier);
        }
        nipple.identifier = identifier;
    
        var process = function (nip) {
            // Trigger the start.
            nip.trigger('start', nip);
            self.trigger('start ' + nip.id + ':start', nip);
    
            nip.show();
            if (pressure > 0) {
                self.pressureFn(evt, nip, nip.identifier);
            }
            // Trigger the first move event.
            self.processOnMove(evt);
        };
    
        // Transfer it from idles to actives.
        if ((indexInIdles = self.idles.indexOf(nipple)) >= 0) {
            self.idles.splice(indexInIdles, 1);
        }
    
        // Store the nipple in the actives array
        self.actives.push(nipple);
        self.ids.push(nipple.identifier);
    
        if (opts.mode !== 'semi') {
            process(nipple);
        } else {
            // In semi we check the distance of the touch
            // to decide if we have to reset the nipple
            var distance = u.distance(position, nipple.position);
            if (distance <= opts.catchDistance) {
                process(nipple);
            } else {
                nipple.destroy();
                self.processOnStart(evt);
                return;
            }
        }
    
        return nipple;
    };
    
    Collection.prototype.getOrCreate = function (identifier, position) {
        var self = this;
        var opts = self.options;
        var nipple;
    
        // If we're in static or semi, we might already have an active.
        if (/(semi|static)/.test(opts.mode)) {
            // Get the active one.
            // TODO: Multi-touche for semi and static will start here.
            // Return the nearest one.
            nipple = self.idles[0];
            if (nipple) {
                self.idles.splice(0, 1);
                return nipple;
            }
    
            if (opts.mode === 'semi') {
                // If we're in semi mode, we need to create one.
                return self.createNipple(position, identifier);
            }
    
            console.warn('Coudln\'t find the needed nipple.');
            return false;
        }
        // In dynamic, we create a new one.
        nipple = self.createNipple(position, identifier);
        return nipple;
    };
    
    Collection.prototype.processOnMove = function (evt) {
        var self = this;
        var opts = self.options;
        var identifier = self.manager.getIdentifier(evt);
        var nipple = self.nipples.get(identifier);
    
        if (!nipple) {
            // This is here just for safety.
            // It shouldn't happen.
            console.error('Found zombie joystick with ID ' + identifier);
            self.manager.removeIdentifier(identifier);
            return;
        }
    
        nipple.identifier = identifier;
    
        var size = nipple.options.size / 2;
        var pos = {
            x: evt.pageX,
            y: evt.pageY
        };
    
        var dist = u.distance(pos, nipple.position);
        var angle = u.angle(pos, nipple.position);
        var rAngle = u.radians(angle);
        var force = dist / size;
    
        // If distance is bigger than nipple's size
        // we clamp the position.
        if (dist > size) {
            dist = size;
            pos = u.findCoord(nipple.position, dist, angle);
        }
    
        var xPosition = pos.x - nipple.position.x
        var yPosition = pos.y - nipple.position.y
    
        if (opts.lockX){
            yPosition = 0
        }
        if (opts.lockY) {
            xPosition = 0
        }
    
        nipple.frontPosition = {
            x: xPosition,
            y: yPosition
        };
    
        if (!opts.dataOnly) {
            u.applyPosition(nipple.ui.front, nipple.frontPosition);
        }
    
        // Prepare event's datas.
        var toSend = {
            identifier: nipple.identifier,
            position: pos,
            force: force,
            pressure: evt.force || evt.pressure || evt.webkitForce || 0,
            distance: dist,
            angle: {
                radian: rAngle,
                degree: angle
            },
            instance: nipple,
            lockX: opts.lockX,
            lockY: opts.lockY
        };
    
        // Compute the direction's datas.
        toSend = nipple.computeDirection(toSend);
    
        // Offset angles to follow units circle.
        toSend.angle = {
            radian: u.radians(180 - angle),
            degree: 180 - angle
        };
    
        // Send everything to everyone.
        nipple.trigger('move', toSend);
        self.trigger('move ' + nipple.id + ':move', toSend);
    };
    
    Collection.prototype.processOnEnd = function (evt) {
        var self = this;
        var opts = self.options;
        var identifier = self.manager.getIdentifier(evt);
        var nipple = self.nipples.get(identifier);
        var removedIdentifier = self.manager.removeIdentifier(nipple.identifier);
    
        if (!nipple) {
            return;
        }
    
        if (!opts.dataOnly) {
            nipple.hide(function () {
                if (opts.mode === 'dynamic') {
                    nipple.trigger('removed', nipple);
                    self.trigger('removed ' + nipple.id + ':removed', nipple);
                    self.manager
                        .trigger('removed ' + nipple.id + ':removed', nipple);
                    nipple.destroy();
                }
            });
        }
    
        // Clear the pressure interval reader
        clearInterval(self.pressureIntervals[nipple.identifier]);
    
        // Reset the direciton of the nipple, to be able to trigger a new direction
        // on start.
        nipple.resetDirection();
    
        nipple.trigger('end', nipple);
        self.trigger('end ' + nipple.id + ':end', nipple);
    
        // Remove identifier from our bank.
        if (self.ids.indexOf(nipple.identifier) >= 0) {
            self.ids.splice(self.ids.indexOf(nipple.identifier), 1);
        }
    
        // Clean our actives array.
        if (self.actives.indexOf(nipple) >= 0) {
            self.actives.splice(self.actives.indexOf(nipple), 1);
        }
    
        if (/(semi|static)/.test(opts.mode)) {
            // Transfer nipple from actives to idles
            // if we're in semi or static mode.
            self.idles.push(nipple);
        } else if (self.nipples.indexOf(nipple) >= 0) {
            // Only if we're not in semi or static mode
            // we can remove the instance.
            self.nipples.splice(self.nipples.indexOf(nipple), 1);
        }
    
        // We unbind move and end.
        self.manager.unbindDocument();
    
        // We add back the identifier of the idle nipple;
        if (/(semi|static)/.test(opts.mode)) {
            self.manager.ids[removedIdentifier.id] = removedIdentifier.identifier;
        }
    };
    
    // Remove destroyed nipple from the lists
    Collection.prototype.onDestroyed = function(evt, nipple) {
        var self = this;
        if (self.nipples.indexOf(nipple) >= 0) {
            self.nipples.splice(self.nipples.indexOf(nipple), 1);
        }
        if (self.actives.indexOf(nipple) >= 0) {
            self.actives.splice(self.actives.indexOf(nipple), 1);
        }
        if (self.idles.indexOf(nipple) >= 0) {
            self.idles.splice(self.idles.indexOf(nipple), 1);
        }
        if (self.ids.indexOf(nipple.identifier) >= 0) {
            self.ids.splice(self.ids.indexOf(nipple.identifier), 1);
        }
    
        // Remove the identifier from our bank
        self.manager.removeIdentifier(nipple.identifier);
    
        // We unbind move and end.
        self.manager.unbindDocument();
    };
    
    // Cleanly destroy the manager
    Collection.prototype.destroy = function () {
        var self = this;
        self.unbindEvt(self.options.zone, 'start');
    
        // Destroy nipples.
        self.nipples.forEach(function(nipple) {
            nipple.destroy();
        });
    
        // Clean 3DTouch intervals.
        for (var i in self.pressureIntervals) {
            if (self.pressureIntervals.hasOwnProperty(i)) {
                clearInterval(self.pressureIntervals[i]);
            }
        }
    
        // Notify the manager passing the instance
        self.trigger('destroyed', self.nipples);
        // We unbind move and end.
        self.manager.unbindDocument();
        // Unbind everything.
        self.off();
    };
    
    /* global u, Super, Collection */
    
    ///////////////////////
    ///     MANAGER     ///
    ///////////////////////
    
    function Manager (options) {
        var self = this;
        self.ids = {};
        self.index = 0;
        self.collections = [];
    
        self.config(options);
        self.prepareCollections();
    
        // Listen for resize, to reposition every joysticks
        var resizeTimer;
        u.bindEvt(window, 'resize', function (evt) {
            clearTimeout(resizeTimer);
            resizeTimer = setTimeout(function () {
                var pos;
                var scroll = u.getScroll();
                self.collections.forEach(function (collection) {
                    collection.forEach(function (nipple) {
                        pos = nipple.el.getBoundingClientRect();
                        nipple.position = {
                            x: scroll.x + pos.left,
                            y: scroll.y + pos.top
                        };
                    });
                });
            }, 100);
        });
    
        return self.collections;
    };
    
    Manager.prototype = new Super();
    Manager.constructor = Manager;
    
    Manager.prototype.prepareCollections = function () {
        var self = this;
        // Public API Preparation.
        self.collections.create = self.create.bind(self);
        // Listen to anything
        self.collections.on = self.on.bind(self);
        // Unbind general events
        self.collections.off = self.off.bind(self);
        // Destroy everything
        self.collections.destroy = self.destroy.bind(self);
        // Get any nipple
        self.collections.get = function (id) {
            var nipple;
            self.collections.every(function (collection) {
                if (nipple = collection.get(id)) {
                    return false;
                }
                return true;
            });
            return nipple;
        };
    };
    
    Manager.prototype.create = function (options) {
        return this.createCollection(options);
    };
    
    // Collection Factory
    Manager.prototype.createCollection = function (options) {
        var self = this;
        var collection = new Collection(self, options);
    
        self.bindCollection(collection);
        self.collections.push(collection);
    
        return collection;
    };
    
    Manager.prototype.bindCollection = function (collection) {
        var self = this;
        var type;
        // Bubble up identified events.
        var handler = function (evt, data) {
            // Identify the event type with the nipple's identifier.
            type = evt.type + ' ' + data.id + ':' + evt.type;
            self.trigger(type, data);
        };
    
        // When it gets destroyed we clean.
        collection.on('destroyed', self.onDestroyed.bind(self));
    
        // Other events that will get bubbled up.
        collection.on('shown hidden rested dir plain', handler);
        collection.on('dir:up dir:right dir:down dir:left', handler);
        collection.on('plain:up plain:right plain:down plain:left', handler);
    };
    
    Manager.prototype.bindDocument = function () {
        var self = this;
        // Bind only if not already binded
        if (!self.binded) {
            self.bindEvt(document, 'move')
                .bindEvt(document, 'end');
            self.binded = true;
        }
    };
    
    Manager.prototype.unbindDocument = function (force) {
        var self = this;
        // If there are no touch left
        // unbind the document.
        if (!Object.keys(self.ids).length || force === true) {
            self.unbindEvt(document, 'move')
                .unbindEvt(document, 'end');
            self.binded = false;
        }
    };
    
    Manager.prototype.getIdentifier = function (evt) {
        var id;
        // If no event, simple increment
        if (!evt) {
            id = this.index;
        } else {
            // Extract identifier from event object.
            // Unavailable in mouse events so replaced by latest increment.
            id = evt.identifier === undefined ? evt.pointerId : evt.identifier;
            if (id === undefined) {
                id = this.latest || 0;
            }
        }
    
        if (this.ids[id] === undefined) {
            this.ids[id] = this.index;
            this.index += 1;
        }
    
        // Keep the latest id used in case we're using an unidentified mouseEvent
        this.latest = id;
        return this.ids[id];
    };
    
    Manager.prototype.removeIdentifier = function (identifier) {
        var removed = {};
        for (var id in this.ids) {
            if (this.ids[id] === identifier) {
                removed.id = id;
                removed.identifier = this.ids[id];
                delete this.ids[id];
                break;
            }
        }
        return removed;
    };
    
    Manager.prototype.onmove = function (evt) {
        var self = this;
        self.onAny('move', evt);
        return false;
    };
    
    Manager.prototype.onend = function (evt) {
        var self = this;
        self.onAny('end', evt);
        return false;
    };
    
    Manager.prototype.oncancel = function (evt) {
        var self = this;
        self.onAny('end', evt);
        return false;
    };
    
    Manager.prototype.onAny = function (which, evt) {
        var self = this;
        var id;
        var processFn = 'processOn' + which.charAt(0).toUpperCase() +
            which.slice(1);
        evt = u.prepareEvent(evt);
        var processColl = function (e, id, coll) {
            if (coll.ids.indexOf(id) >= 0) {
                coll[processFn](e);
                // Mark the event to avoid cleaning it later.
                e._found_ = true;
            }
        };
        var processEvt = function (e) {
            id = self.getIdentifier(e);
            u.map(self.collections, processColl.bind(null, e, id));
            // If the event isn't handled by any collection,
            // we need to clean its identifier.
            if (!e._found_) {
                self.removeIdentifier(id);
            }
        };
    
        u.map(evt, processEvt);
    
        return false;
    };
    
    // Cleanly destroy the manager
    Manager.prototype.destroy = function () {
        var self = this;
        self.unbindDocument(true);
        self.ids = {};
        self.index = 0;
        self.collections.forEach(function(collection) {
            collection.destroy();
        });
        self.off();
    };
    
    // When a collection gets destroyed
    // we clean behind.
    Manager.prototype.onDestroyed = function (evt, coll) {
        var self = this;
        if (self.collections.indexOf(coll) < 0) {
            return false;
        }
        self.collections.splice(self.collections.indexOf(coll), 1);
    };
    
    var factory = new Manager();
    return {
        create: function (options) {
            return factory.create(options);
        },
        factory: factory
    };
    
    });
    